package tg1;

import tg1.JdbcDAO;

import java.util.Map;
import java.util.Date;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

import java.net.InetAddress;

public class BEncoder{

/* o construtor privado e o getBEncoder implementam o padrao singleton*/	
	private static BEncoder bEncoder;
 	private static JdbcDAO JdbcDAO;
	
 
	private BEncoder() {
		JdbcDAO = JdbcDAO.getDAO();
	}
 
	 /****************************************************************************/
	/****************************************************************************/
	
 	/*se nenhuma instancia de BEncoder foi criada ainda, cria uma e retorna
 	senao, retorna a instancia que jah foi criada*/
	public synchronized static BEncoder getEncoder() {
		if( bEncoder == null ) {
			bEncoder = new BEncoder();
		}
		return bEncoder;
	}
	
	/****************************************************************************/
	/****************************************************************************/
	
	//public static String getBEncodedResponse(Map peerDataMap) throws FudeuTudoException{
	public static byte[] getBEncodedResponse(Map peerDataMap) throws Exception{
	
		Connection conn=null;	
		try{
			
			conn = JdbcDAO.getDBConnection();	
			
			StringBuffer bresponse= new StringBuffer();
			bresponse.append("d8:intervali");
			bresponse.append(JdbcDAO.ANNOUNCE_INTERVAL);
			bresponse.append("e12:min intervali");
			bresponse.append(JdbcDAO.MIN_ANNOUNCE_INTERVAL);			
			//bresponse.append("e9:trackerid13:aatrackertest8:completei");
			bresponse.append("e8:completei");
			bresponse.append( JdbcDAO.getPeerAmount(conn, peerDataMap, JdbcDAO.SEEDER));
			bresponse.append("e10:incompletei");
			bresponse.append( JdbcDAO.getPeerAmount(conn, peerDataMap, JdbcDAO.LEECHER));
			bresponse.append("e10:tracker id22:");
			
			byte[] header=  (bresponse.toString()).getBytes("ASCII");
			byte[] peerslistHeader="5:peers".getBytes("ASCII");
			byte[] peerslist= BEncoder.bEncodePeersList(conn, peerDataMap);
			byte[] trailer= "e".getBytes("ASCII");

			
			//byte[] trackerid = BEncoder.getTrackerId(peerDataMap);
			int indexteste=0;
			byte[] trackerid=new byte[22];
			for (indexteste=0; indexteste<22; indexteste++){
					trackerid[indexteste]= (byte) 'A';
			}
			
			
			int responseSize = header.length + trackerid.length +peerslistHeader.length+ peerslist.length + trailer.length ;
			
			byte[] byteResponse = new byte[responseSize];
System.out.println(" byteResponse.length"+byteResponse.length);
			
			int i =0;
			for (i=0; i<header.length; i++){
					byteResponse[i]= header[i];
			}
System.out.println("Header copiado");			
			int length = i;
			for (i=0; i<trackerid.length; i++){
					byteResponse[length]= trackerid[i];
					length++;
			}
System.out.println("Trackerid copiado");						
			for (i=0; i<peerslistHeader.length; i++){
					
					byteResponse[length]= peerslistHeader[i];
					length++;
			}
System.out.println("Header da peerslist copiado");			
			for (i=0; i<peerslist.length; i++){
					
					byteResponse[length]= peerslist[i];
					length++;
			}
System.out.println("Peerslist copiada");			
			for (i=0; i<trailer.length; i++){
					byteResponse[length]= trailer[i];
			}
System.out.println("Trailer copiada");			

			//conn.close();
			//return bresponse.toString();
			return byteResponse;
			
		}catch (Exception e){
		
			System.out.println ("Erro em getBEncodedResponse:");
			e.printStackTrace();
			throw new IllegalStateException("Erro fechando conexao em getBEncodedResponse");
		}
		finally{conn.close();}
	}
	

	/****************************************************************************/
	/****************************************************************************/
	/*o trackerid eh uma string de bytes composta dos seguintes campos:
	ip e porta do tracker que tah processando o request 
	horario que o processamento foi feito (milissegundos desde a epoch Unix)
	e o ratio do peer desse announce*/
	private static byte[] getTrackerId(Map peerDataMap){
		try{
			int i=0, aux=0;
			byte[] rawip =  InetAddress.getLocalHost().getAddress();
			byte[] trackerid = new byte[22];
			
			for (i=0; i<4; i++){
				trackerid[i]=rawip[i];
			}
	
			int port = ((Integer)peerDataMap.get("port")).intValue();
			trackerid[4]= (byte)(port>>>8);
			trackerid[5]= (byte)port;
	
			Date date = new Date();
			long millisSinceEpoch= date.getTime();
			for (i=7; i>=0; i--){
				trackerid[i+5]=(byte) (millisSinceEpoch >>>(i*8));
			}
			trackerid[13]=(byte) millisSinceEpoch;
	
			long ratio =Double.doubleToLongBits( JdbcDAO.getUserRatio(peerDataMap));
	
			for (i=7; i>=0; i--){
				trackerid[i+13]=(byte) (ratio >>>(i*8));
			}
			trackerid[21]=(byte) ratio;
			System .out.println("Trackerid gerado");
			return trackerid;

		}catch (Exception e){
				System.out.println ("Erro em bEncodePeersList:");
				e.printStackTrace();
				return null;
		}
	}

	/****************************************************************************/
	/****************************************************************************/
	//private static String bEncodePeersList(Connection conn, Map peerDataMap) throws FudeuTudoException{
	private static byte[] bEncodePeersList(Connection conn, Map peerDataMap) throws Exception{
			try{
				ResultSet rs = JdbcDAO.getPeers(conn, peerDataMap);
				//String peerslist;
				byte[] peerslist;
				/*recuperando lista de peers encodados numa string*/		
				if ((peerDataMap.get("compact")!= null) && (peerDataMap.get("compact").equals("1"))){				
					peerslist= BEncoder.getCompactPeersDictionary(rs);			
				}	
				else {
					peerslist= BEncoder.getFullPeersDictionary(rs);
				}
				rs.close();
				conn.close();
				return peerslist;
				
			}catch (Exception e){
				System.out.println ("Erro em bEncodePeersList:");
				e.printStackTrace();
				
				throw new IllegalStateException("Erro em bEncodePeersList");
					
			}
	}

	/****************************************************************************/
	/****************************************************************************/

	/*criando string com peer_id no response*/
	/** aqui eh o seguinte:usa mais ou menos 50 bytes cada dicionario, 
	* e dah +/- 2200 bytes (2,2K) pra compor um dicionario de 30 peers
	* se tiver menos de 30 peers, alocou memoria pra nada, e dai eh problema... 
	* entao aloca metade disso pra naum gastar muito 
	* e se precisar mais dexa que a jvm se vira xD*/
	//private static String getFullPeersDictionary(ResultSet rs) throws Exception{
	private static byte[] getFullPeersDictionary(ResultSet rs) throws Exception{

		StringBuffer sb = new StringBuffer(1100);	
		sb.append("l");
		int numPeers=0;
		while (rs.next()){
			sb.append("d7:peer_id20:");
			sb.append(rs.getString ("peer_id"));
			sb.append("2:ip");
		
			sb.append(rs.getString ("ip"));
			sb.append("4:port");
			/* a porta naum precisa*/
			sb.append("i");
			sb.append(rs.getInt("port"));
			/*um e pro int, otro pro dicionario*/
			sb.append("ee");
			numPeers++;
		}
		sb.append("e");
		//return sb.toString();
		return sb.toString().getBytes("ASCII");
	}
	
	/****************************************************************************/
	/****************************************************************************/
	
	/*criando string SEM peer_id no response*/
	/** se aqui eh o seguinte: usa mais 6 bytes cada ip com porta, entao
	* aloca 180 bytes pra compor uma lista de 30 peers
	* se tiver menos de 30 peers, alocou memoria pra nada, e dai eh problema... 
	* mas dane-se, supoe-se que o servidor seja bom xD
	* (e ainda assim eh bem melhor que alocar 2,2k como no metodo acima)*/
 	private static byte[] getCompactPeersDictionary(ResultSet rs) throws Exception{	

		//ResultSetMetaData rsmd= rs.getMetaData();
		int numrows=0;
		int port=0;
		if (rs.next()){
			rs.last();	
			numrows= rs.getRow();
			port =rs.getInt("port");
			rs.beforeFirst();
		}
		//else {return null;}
System.out.println("numero de peers encontrados no torrent:"+numrows);
		int i=0;
		
		byte[] peerListSize= (String.valueOf(numrows*6)+":").getBytes("ASCII");

		byte[] peerByteResponse =  new byte[(numrows*6)+peerListSize.length];
System.out.println("peerByteResponse"+peerByteResponse.length);
		for (i=0; i<peerListSize.length;i++){
			peerByteResponse[i]=peerListSize[i];	
		}

		int actualLength=0;
		int peerAtual=0;
		//int port =65535;
		
//System.out.println("jaspion");
		while (rs.next()){
//System.out.println("jaspion2");
			actualLength= (peerAtual*6)+(peerListSize.length);
//System.out.println("actualLength="+actualLength);
			byte[] ipembytes =rs.getBytes("ipembytes"); 

			peerByteResponse[ actualLength] = ipembytes[0];
			peerByteResponse[ actualLength+1] = ipembytes[1] ;
			peerByteResponse[ actualLength+2] = ipembytes[2] ;
			peerByteResponse[ actualLength+3] = ipembytes[3] ;

			/*peerByteResponse[ actualLength] = (byte) (ipembytes[0] | 255);;
			peerByteResponse[ actualLength+1] =(byte) (ipembytes[1] | 255);
			peerByteResponse[ actualLength+2] =(byte) (ipembytes[2] | 255);
			peerByteResponse[ actualLength+3] = (byte)(ipembytes[3] | 255);
*/
			peerByteResponse[actualLength+4]= (byte)(port >>>8);
		        peerByteResponse[actualLength+5]= (byte)port;
			actualLength= peerByteResponse.length;
			peerAtual++;
//System.out.println("jaspion2");
			
		}
//System.out.println("actualLength="+actualLength);
		return peerByteResponse;
}
//implementacao original
	//private static String getCompactPeersDictionary(ResultSet rs) throws Exception{
// 	
// 		StringBuffer sb = new StringBuffer(300);	
// 		int numPeers=0;
// 		while (rs.next()){
// 			
// 			byte[] ipembytes =rs.getBytes("ipembytes"); 
// 			
// 			int i=0;
// 		
// 			char[] ip= new char[4];
// 			/*for (i=0; i<4;i++){
// 				ip[i]=(char) ipembytes[i];
// 			}
// 			*/
// 			for (i=0; i<4;i++){
// 				//sb.append(((char) (ipembytes[i] | 65535)));
// 				sb.append(((char) ipembytes[i] ));
// 			}
// 			System.out.println("RESPONSELENGTH********"+sb.length());
// 			System.out.println("BENCODED********IP");
// 
// 
// 			for (i=0; i<4;i++){
// 				System.out.print(ipembytes[i]);
// 				System.out.print("=>");
// 				System.out.println((char) ipembytes[i]);
// 			}
// 			//sb.append(ip);
// 			
//  			//short port =(short)  65535;
// 			//short port =(short)  32767;
// 			//short port =(short)  16834;
// 			//short port =(short)  16706;
// 			//int port = 255;
// 
// 			//int port =  16834;
// 			//int port =  32767;
// 			//int port =  16706;
// 			int port= 65535;
// 			//int port = rs.getInt("port");
// 			//short mask =(short) 255;
// 			int mask = 255;
// 			System.out.println("porta********"+port);
// 			//short aux1 = (short) ((short) port >>>(short)8);
// 			int aux1 =  ( port >>>8);
// 			char[] jaspion = new char[2];
// 			jaspion[0]=(char) aux1;
// 			jaspion[1]=	(char) (port&mask);			
// 			//jaspion[0]=(char)1;
// 			//jaspion[1]=(char)129;			
// 			System.out.println("BENCODED********porta"+jaspion[0]+"&"+jaspion[1]);
// 			//sb.append(jaspion[0]);
// 			//sb.append(jaspion[1]);
// 			sb.append(jaspion);
// 			System.out.println("RESPONSELENGTH2********"+sb.length());
// 			
// 			//sb.append((char) port);
// 	/*		short aux1 = (short) ((short) port >>>(short)16);
// 			char charteste =(char)aux1; 
// 			sb.append(charteste);
// */
// 			numPeers++;
// 		}
// 
// 			byte[] lala=sb.toString().getBytes("ASCII");
// 			System.out.println("RESPONSELENGTH3********"+lala.length);
// 		sb.insert(0, sb.length()+":");
// 		System.out.println("numPeers********"+numPeers);
// 		//sb.insert(0,( numPeers*6)+":");
// 	//	System.out.println("peersBencoded= "+sb.toString());
// 		return sb.toString();
// 		
// 	}
	
	/****************************************************************************/
	/****************************************************************************/
	//public static String bencodeScrapeResponse(Map peerDataMap) throws FudeuTudoException{
	public static byte[] bencodeScrapeResponse(Map peerDataMap) throws Exception{

		Connection conn=null;
		try{
			
			conn = JdbcDAO.getDBConnection();	
			
			StringBuffer bresponse= new StringBuffer();
			bresponse.append("d5:filesd");
			int i=0;
			//int mapSize=peerDataMap.size();
			int numHashes = ((Integer) peerDataMap.get("numHashes")).intValue();
			System.out.println("numHashes= "+numHashes);
			for (i=0; i<numHashes; i++){
				bresponse.append("20:");
				bresponse.append(peerDataMap.get("info_hash"+i));
				bresponse.append("d8:completei");
				bresponse.append( JdbcDAO.getPeerAmount(conn, peerDataMap, JdbcDAO.SEEDER));
				bresponse.append("e10:incompletei");
				bresponse.append( JdbcDAO.getPeerAmount(conn, peerDataMap, JdbcDAO.LEECHER));
				bresponse.append("e10:downloadedi");
				bresponse.append(JdbcDAO.getCompletedDownloadsAmount(conn, peerDataMap));
			
				//um "e" pra fechar o "i", outro pra fechar o value do info_hash
				bresponse.append("ee");
			}
			//um "e" pra fechar a files e outro pro dicionario que abre a resposta
			bresponse.append("ee");
		
			//conn.close();
			//return bresponse.toString();
			return bresponse.toString().getBytes("ASCII");
		}catch (Exception e){
			if (conn != null){
				try{
					//conn.close();	
				}catch (Exception e2){
					System.out.println ("Erro fechando conexao em bencodeScrapeResponse:");
					e2.printStackTrace();
				}
			}				
			
			System.out.println ("Erro fechando conexao em bencodeScrapeResponse:");
			e.printStackTrace();
			
			throw new IllegalStateException("Erro fechando conexao em bencodeScrapeResponse", e);
		}
		finally {conn.close();}
		
	
	}		
			
	/****************************************************************************/
	/****************************************************************************/
	//public static String bencodeErrorMessage(String errorMessage) {
	public static byte[] bencodeErrorMessage(String errorMessage) {	

		try{
			return( ("d14:failure reason"+errorMessage.length()+":"+errorMessage+"e").getBytes("ASCII"));
		}catch (Exception e){
					System.out.println ("Erro fechando conexao em bencodeErrorMessage:");
					e.printStackTrace();
					return null;
		}
	}
}	
